﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Controls;

namespace FIFP.Common
{    
    public static class ViewModelExtensionMethods
    {
        public static ObservableViewModelCollection<TViewModel, TModel> WrapWithViewModel<TViewModel, TModel>(this ObservableCollection<TModel> source, Func<TModel, TViewModel> factory)
           where TViewModel : ViewModelCore
        {
            return new ObservableViewModelCollection<TViewModel, TModel>(source, factory);
        }       
    }

    public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
        where TViewModel : ViewModelCore
    {
        private readonly ObservableCollection<TModel> _source;
        private readonly Func<TModel, TViewModel> _viewModelFactory;
        private WeakEventListener<ObservableViewModelCollection<TViewModel, TModel>, ObservableCollection<TModel>, NotifyCollectionChangedEventArgs> _eventHandler;       

        public ObservableViewModelCollection(ObservableCollection<TModel> source, Func<TModel, TViewModel> viewModelFactory)
            : base(source.Select(viewModelFactory))
        {
            if (source == null)
                throw new ArgumentNullException("source", "ObservableViewModelCollection: source cannot be null");

             _source = source;
             _viewModelFactory = viewModelFactory;

             WireEventHandler();
        }

        private void WireEventHandler()
        {
            _eventHandler = new WeakEventListener<ObservableViewModelCollection<TViewModel, TModel>, ObservableCollection<TModel>, NotifyCollectionChangedEventArgs>(this, _source);
            _source.CollectionChanged += _eventHandler.OnEvent;

            _eventHandler.OnEventAction = (instance, sourceObject, e) =>
            {
                instance.OnSourceCollectionChanged(instance, e);
            };

            _eventHandler.OnDetachAction = (listener, sourceObject) =>
            {
                sourceObject.CollectionChanged -= listener.OnEvent;
            };
        }

        protected virtual TViewModel CreateViewModel(TModel model)
        {
            return _viewModelFactory(model);
        }        

        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:

                    if (e.NewItems == null)
                        break;
                    
                    for (int i = 0; i < e.NewItems.Count; i++)
                    {
                        InsertItem(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                    }
                    break;                

                case NotifyCollectionChangedAction.Remove:

                    if (e.OldItems == null)
                        break;

                    for (int i = 0; i < e.OldItems.Count; i++)
                    {
                        RemoveItem(e.OldStartingIndex);
                    }
                    
                    break;

                case NotifyCollectionChangedAction.Replace:
                    // remove
                    if (e.OldItems == null)
                        break;
                    foreach (object t in e.OldItems)
                    {
                        RemoveItem(e.OldStartingIndex);
                    }
                    // add
                    goto case NotifyCollectionChangedAction.Add;

                case NotifyCollectionChangedAction.Reset:

                    Clear();

                    if (e.NewItems != null)
                    {
                        for (int i = 0; i < e.NewItems.Count; i++)
                        {
                            InsertItem(e.NewStartingIndex + i, CreateViewModel((TModel) e.NewItems[i]));
                        }
                    }

                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    
                    break;

                default:
                    break;
            }
        }
    }
}
